<?php

/************************************************************
 * Copyright (C), 1993, Dacelve. Tech., Ltd.
 * FileName : Server.php
 * Author   : Lizhijian
 * Version  : 1.0
 * Date     : 2018/1/17 15:18
 * Description   :
 * Function List :
 * History  :
 * <author>    <time>    <version >    <desc>
 * Lizhijian   2018/1/17   1.0          init
 * Lizhijian   2018/2/12   1.1          optimize
 ***********************************************************/

/**
 * webSocket服务器通用应用类
 * Class Server
 */
class Server
{
    /**
     * 状态信息
     */
    const SEND_USER_LIST_NUM = 50;//分批发送用户列表时的人数
    const OK_DATA_STATUS = 200;
    const ERROR_DATA_STATUS = 201;
    const ERROR_DATA_EMPTY = 'ERROR_DATA_EMPTY: data is empty!';
    const ERROR_DATA_MSG_EMPTY = 'ERROR_DATA_MSG_EMPTY: data msg is empty!';
    const ERROR_DATA_FORMAT = 'ERROR_DATA_FORMAT: data format is error! (Tip:must json data)';
    const ERROR_DATA_NUMBER = 'ERROR_DATA_NUMBER: data number is error! (Tip:must true number)';
    const ERROR_PACK_EMPAY = 'ERROR_PACK_EMPAY: data mapping pack is empty! (Tip:must true cmd)';
    const ERROR_PACK_CMD = 'ERROR_PACK_CMD: data mapping pack cmd is error!';
    const ERROR_PACK_FORMAT = 'ERROR_PACK_FORMAT: data mapping pack format is error!';
    const OK_DATA_MSG = 'OK';//验证协议成功时使用
    const ERROR_DATA_MSG = 'ERROR';//验证协议失败时使用
    const ERROR_SPEAK = '你已被禁言！';//验证协议失败时使用
    const ERROR_SPEAK_STATUS = 205;//验证协议失败时使用
    const VISITOR_HEAD = [//游客头像
                          'https://my-aliyun-test.oss-cn-shenzhen.aliyuncs.com/uploads/5059454.png',
                          'https://my-aliyun-test.oss-cn-shenzhen.aliyuncs.com/uploads/t012fbf748155a94520.jpg',
                          'https://my-aliyun-test.oss-cn-shenzhen.aliyuncs.com/uploads/u%3D1691359396%2C1746939139%26fm%3D27%26gp%3D0.jpg',
                          'https://my-aliyun-test.oss-cn-shenzhen.aliyuncs.com/uploads/u%3D2567772611%2C3197194756%26fm%3D27%26gp%3D0.jpg',
                          'https://my-aliyun-test.oss-cn-shenzhen.aliyuncs.com/uploads/u%3D3957970105%2C3667748536%26fm%3D27%26gp%3D0.jpg',
    ];
    const VISITOR_NAME = '游客';//游客名
    const PACK_LIST = [
        'getPackList' => [//获取现有协议包
                          'cmd'
        ],
        'ping'        => [//心跳包
                          'cmd'
        ],
        'msg'         => [//消息包(type=0广播、1组播、2互播)
                          'cmd', 'msg', 'toMsg', 'srcClientId', 'toClientId', 'userId', 'room', 'group', 'data', 'type'
        ],
        'speak'       => [//禁言
                          'cmd', 'msg', 'srcClientId', 'toClientId', 'notAllowUserId', 'group', 'room', 'data', 'time'
        ],
        'gift'        => [//送礼
                          'cmd', 'msg', 'srcClientId', 'toClientId', 'userId', 'group', 'room', 'data', 'number'
        ],
        'kick'        => [//踢出
                          'cmd', 'msg', 'srcClientId', 'toClientId', 'userId', 'group', 'room', 'data'
        ],
        'login'       => [//登录
                          'cmd', 'type', 'userId', 'room', 'group'
        ],
        'userList'    => [//用户列表
                          'cmd', 'clientId'
        ],
        'ad'          => [//全站公告
                          'cmd', 'msg', 'data'
        ],
        'getUsrList'  => [//获取用户列表
                          'cmd', 'msg', 'data'
        ],
    ];
    const MYSQL = [
        'host' => '120.79.209.186',
        'port' => 3306,
        'user' => 'admin',
        'pwd'  => 'admin123',
        'db'   => 'fast',
    ];

    /**
     * 服务器实例
     * @var
     */
    protected static $server;

    /**
     * 数据库实例
     * @var
     */
    protected static $db;

    /**
     * 用户列表数据
     * @var array
     */
    protected static $userList;

    public static function __init($serv)
    {
        if (empty(self::$server)) {
            self::$server = $serv;
        }
    }

    public static function connectMysql()
    {
        if (!self::$db) {
            self::$db = new swoole_client(SWOOLE_SOCK_TCP);
            self::$db->connect(self::MYSQL['host'], 9508, 10) or die("连接失败");
            if (!self::$db) {
                echo "ERROR";
            }
        }
    }

    public static function find($sql)
    {
        $res = self::query($sql);
        return $res[0];
    }



    public static function query($sql)
    {
        self::connectMysql();
        self::$db->send($sql);
        $res = self::$db->recv();//阻塞接受返回的结果
        return json_decode($res, true);
    }

    /**
     * 检查数据包是否正确
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $data
     * @param string $msg
     * @param int $code
     * @return array|string
     * @example
     */
    public static function check($data, $msg = self::OK_DATA_MSG, $code = self::OK_DATA_STATUS)
    {
        //是否空数据
        if (empty($data)) {
            return self::back(self::ERROR_DATA_EMPTY, '', 1, 201);
        }
        //是否JSON格式
        if (!self::is_json($data)) {
            return self::back(self::ERROR_DATA_FORMAT, '', 1, 201);
        }
        //是否主指令字段错误
        $data = json_decode($data, true);
        if (false === isset($data['cmd'])) {
            return self::back(self::ERROR_PACK_CMD, '', 1, 201);
        }
        //是否对应包不存在
        $tmp = self::PACK_LIST;
        if (empty($tmp[$data['cmd']])) {
            return self::back(self::ERROR_PACK_EMPAY, '', 1, 201);
        }
        //消息为空
        if ($data['cmd'] == 'msg' && $data['type'] == 0 && $data['msg'] == '') {
            return self::back(self::ERROR_DATA_MSG_EMPTY, '', 1, 201);
        }
        //是否字段数量错误
        $keys    = array_keys($data);
        $pacKeys = array_keys(array_flip(self::PACK_LIST[$data['cmd']]));
        $diff    = array_diff($pacKeys, $keys);
        if ($diff) {
            return self::back(self::ERROR_DATA_NUMBER, $diff, 1, 201);
        }
        //是否字段格式错误
        $sameCount = count(array_intersect($keys, $pacKeys));
        if ($sameCount != count($pacKeys)) {
            return self::back(self::ERROR_PACK_FORMAT, '', 1, 201);
        }

        return self::back('check ok', $data, 1, $code);
    }

    /**
     * 判断JSON格式
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $str
     * @return bool
     * @example
     */
    protected static function is_json($str)
    {
        if (is_array($str)) {
            return false;
        }
        $str = is_object(json_decode($str));
        switch ($str) {
            case 1;
                return true;
                break;
            default;
                return false;
                break;
        }
    }

    /**
     * 统一返回给客户端的方法
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param string $msg
     * @param array $data
     * @param int $type 0：json 1:array [数组形式用于验证时使用]
     * @param int $code
     * @return array|string
     * @example
     */
    public static function back($msg = self::OK_DATA_MSG, $data = [], $type = 0, $code = self::OK_DATA_STATUS)
    {
        $back = [
            'code' => $code,
            'msg'  => $msg,
            'data' => $data,
        ];
        if (0 == $type) {
            $back = json_encode($back);
        }
        return $back;
    }

    /**
     * 获取现有协议包
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @return array
     * @example
     */
    public static function getPackList()
    {
        return self::PACK_LIST;
    }

    /**
     * 发送消息到所有客户端
     * @param $serv
     * @param $msg
     * @param $data
     * @author Lizhijian
     */
    public static function sendToAll($data = [], $msg = '')
    {
        foreach (self::$server->connections as $v) {
            self::$server->push($v, self::back($msg, $data));
        }
    }

    /**
     * 发送消息到房间内所有客户端
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $serv
     * @param string $msg
     * @param array $data
     * @param $room
     * @example
     */
    public static function sendLoginToRoom($data = [], $room, $msg = '')
    {
        foreach (self::$server->connections as $v) {
            $sendUserInfo = self::$server->userTable[$v];
            if (isset($sendUserInfo) && $sendUserInfo['room'] == $room) {
                if ($data['userId'] == $sendUserInfo['userId']) {//是当前用户
                    $data['userName'] = $sendUserInfo['userName'];
                }
                self::$server->push($v, self::back($msg, $data));
            }
        }
    }

    public static function sendToRoom($data = [], $room, $msg = '')
    {
        foreach (self::$server->connections as $v) {
            $sendUserInfo = self::$server->userTable[$v];
            $userInfo     = self::$server->userTable[$data['srcClientId']];
            if (isset($userInfo) && $sendUserInfo['room'] == $room) {
                $back = array_combine(self::PACK_LIST['msg'], [
                    $data['cmd'],
                    isset($data['msg']) ? $data['msg'] : ' ',
                    isset($data['toMsg']) ? $data['toMsg'] : ' ',
                    $data['srcClientId'],
                    $data['toClientId'],
                    $data['userId'],
                    $data['room'],
                    $data['group'],
                    $data['data'],
                    isset($data['type']) ? $data['type'] : 0
                ]);

                $back['userName'] = $userInfo['userName'];
                $back['headImg']  = $userInfo['headImg'];
                self::$server->push($v, self::back($msg, $back));
            }
        }
    }

    /**
     * 发送消息到房间某个组所有客户端
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $serv
     * @param string $msg
     * @param array $data
     * @param $room
     * @param $group
     * @example.
     */
    public static function sendToGroup($data = [], $room, $group, $msg = '')
    {
        foreach (self::$server->connections as $v) {
            $userInfo    = self::$server->userTable[$v];
            $srcUserInfo = self::$server->userTable[$data['srcClientId']];
            if (isset($userInfo) && $userInfo['room'] == $room && $userInfo['group'] == $group) {
                $back = array_combine(self::PACK_LIST['msg'], [
                    $data['cmd'],
                    isset($data['msg']) ? $data['msg'] : ' ',
                    isset($data['toMsg']) ? $data['toMsg'] : ' ',
                    $data['srcClientId'],
                    $data['toClientId'],
                    $data['userId'],
                    $data['room'],
                    $data['group'],
                    $data['data'],
                    isset($data['type']) ? $data['type'] : 0
                ]);

                $back['userName'] = $srcUserInfo['userName'];
                $back['headImg']  = $srcUserInfo['headImg'];

                self::$server->push($v, self::back($msg, $back));
            }
        }
    }

    /**
     * 发送消息给某个客户端
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $serv
     * @param $fd
     * @param string $msg
     * @param array $data
     * @example
     */
    public static function sendToOne($fd, $data = [], $msg = self::OK_DATA_MSG, $code = self::OK_DATA_STATUS)
    {
        $userInfo = self::$server->userTable[$fd];
        if (empty($data['userName'])) {
            $data['userName'] = $userInfo['userName'];
        }
        if (empty($data['headImg'])) {
            $data['headImg'] = $userInfo['headImg'];
        }
        self::$server->push($fd, self::back($msg, $data, 0, $code));
    }

    /**
     * 记录日志
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $data
     * @example
     */
    public static function record($data)
    {
        var_dump($data);
    }

    public static function showTable($table)
    {
        foreach ($table as $v) {
            var_dump($v);
        }
    }

    /**
     * 服务器调用本方法会变成守护进程
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @example
     */
    public static function daemonize()
    {
        $pid = pcntl_fork();
        if ($pid == -1) {
            die("fork(1) failed!\n");
        } elseif ($pid > 0) {
            //让由用户启动的进程退出
            exit(0);
        }

        //建立一个有别于终端的新session以脱离终端
        posix_setsid();

        $pid = pcntl_fork();
        if ($pid == -1) {
            die("fork(2) failed!\n");
        } elseif ($pid > 0) {
            //父进程退出, 剩下子进程成为最终的独立进程
            exit(0);
        }
    }

    /**
     * 获取连接数量
     * @name
     * @Description
     * @remark
     * @author Lizhijian
     * @param $server
     * @example
     */
    public static function getConnectNum()
    {
        echo "当前服务器共有 " . count(self::$server->connections) . " 个连接\n";
    }

    public static function sendUserListToLoginer($fd, $msg = '')
    {
        $send         = $data = [];
        $send['code'] = 200;
        $send['cmd']  = 'userList';

        $LoginInfo = self::$server->userTable[$fd];
        foreach (self::$server->connections as $v) {
            $userInfo = self::$server->userTable[$v];
            if ($userInfo['room'] != $LoginInfo['room']) {
                continue;
            }
            $data[] = $userInfo;
        }
        //用户分批发送
        $size = self::SEND_USER_LIST_NUM;
        $tmp  = array_chunk($data, $size);
        foreach ($tmp as $v_data) {
            self::$server->after(10, function () use ($fd, $msg, $v_data, $send) {
                $send['data'] = $v_data;
                self::$server->push($fd, self::back($msg, $send));
            });
        }
    }

    public static function sendUserList($loginFd, $msg = '')
    {
        $send         = [];
        $send['code'] = 200;
        $send['cmd']  = 'userList';

        $loginInfo      = self::$server->userTable[$loginFd];
        $send['data'][] = $loginInfo;
        foreach (self::$server->connections as $v) {
            $toUserInfo = self::$server->userTable[$v];
            if ($v == $loginFd || $loginInfo['room'] != $toUserInfo['room']) {
                continue;
            }
            self::$server->push($v, self::back($msg, $send));
        }
    }

    public static function sendError($fd, $msg, $data = []){
        $send         = [];
        $send['cmd']  = 'error';
        $send['msg']  = $msg;
        $send['data'] = $data;
        self::$server->push($fd, self::back(self::ERROR_DATA_MSG, $send, 0, 201));
    }
}

